
package com.fred

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.ButtonDefaults.buttonColors
import androidx.compose.material.TextFieldDefaults.textFieldColors
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.ComposeWindow
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.scale
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.*
import com.fred.bean.*
import com.fred.dragdrop.PlatformDropTargetModifier
import com.fred.dragdrop.dropTarget
import com.fred.util.*
import com.fred.xlog.Log
import com.fred.xlog.XLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.awt.GraphicsEnvironment
import java.awt.Toolkit
import java.io.File
import javax.swing.JFileChooser
import javax.swing.UIManager
import javax.swing.filechooser.FileNameExtensionFilter


private const val AAR_PATH = "aarPathConfig"
lateinit var coroutineScope: CoroutineScope
private lateinit var devices:SnapshotStateList<PhoneBean>
private lateinit var phoneBean:MutableState<PhoneBean>
private lateinit var pathStr:MutableState<String>
private lateinit var installSteps: SnapshotStateList<String>
private lateinit var installEnable:MutableState<Boolean>
lateinit var parentWindow: ComposeWindow
private val screenSize by lazy {
    Toolkit.getDefaultToolkit().screenSize
}
private val screenInsets by lazy {
    Toolkit.getDefaultToolkit().getScreenInsets(
        GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice.defaultConfiguration)
}

private const val suffix = "aab"
private var aabFile: File? = null
private val filter by lazy {
    FileNameExtensionFilter("*.aab", suffix)
}
val fileChooser by lazy {
    try {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
    } catch (ignored: Exception) {
    }
    JFileChooser().apply {
        fileFilter = filter
        addChoosableFileFilter(filter)
        fileSelectionMode = JFileChooser.FILES_ONLY
    }
}


@Composable
fun errorDialog(title:MutableState<String>, tipContent:MutableState<String>, alertDialog: MutableState<Boolean>) {
    if (alertDialog.value) {
        Dialog(
            onCloseRequest = {
                alertDialog.value = false
            },
            resizable = false,
            icon = painterResource("error_icon.svg"),
            title = title.value,
            state = DialogState(position = getPosition(300f,185.4f),
                size = DpSize(300.dp, 185.4.dp))
        ) {
            Column(modifier = Modifier.fillMaxWidth()
                .fillMaxHeight(),
            horizontalAlignment = Alignment.CenterHorizontally) {
                Spacer(modifier = Modifier.height(8.dp))
                Text(tipContent.value, maxLines = 3,
                    modifier = Modifier.padding(8.dp),
                    overflow = TextOverflow.Ellipsis)
                Spacer(modifier = Modifier.height(8.dp))
                Button(colors = buttonColors(backgroundColor = ColorUtil.primary),
                    onClick = {
                        alertDialog.value = false
                    } ) {
                    Text(text="确  定",color=  ColorUtil.mainFont )
                }
            }
        }
    }
}

@Composable
@Preview
fun App() {
    MaterialTheme(colors = lightColors()) {
        val focusRequester = remember { FocusRequester.Default }
        Column(modifier = Modifier
            .fillMaxHeight()
            .fillMaxWidth(),
            horizontalAlignment = Alignment.Start) {
            menu()
            Column(
                modifier = Modifier
                    .fillMaxHeight()
                    .fillMaxWidth()
                    .padding(16.dp,8.dp,16.dp,16.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {

                selectFile(focusRequester)
                Spacer(modifier = Modifier.height(16.dp))
                Row(modifier = Modifier.fillMaxWidth(),
                    verticalAlignment = Alignment.CenterVertically) {
                    Box(modifier = Modifier.weight(1f),
                        contentAlignment = Alignment.Center) {
                        Dropdown(devices, onMenuItemClick = {
                            phoneBean.value = it
                        },
                            onclick = {
                                coroutineScope.launch(Dispatchers.IO) {
                                    refreshDevices()
                                }
                            },
                            enabled = installEnable.value,
                            lineColor = if (installEnable.value) ColorUtil.mainFont else ColorUtil.disableFont,
                            tip = phoneBean.value.model,
                            minWidth = 130.dp,
                            maxWidth = 162.dp,
                            tipColor = if(!installEnable.value) ColorUtil.disableFont else if (phoneBean.value.transportId >= 0) ColorUtil.mainFont else ColorUtil.errorFont,
                        menuMaxHeight = 220.dp) {
                            Text(
                                it.model, style = if (it.usbName == phoneBean.value.usbName) {
                                    TextStyle.Default.copy(
                                        color = ColorUtil.selectFont,
                                        fontWeight = FontWeight.Bold
                                    )
                                } else {
                                    TextStyle.Default.copy(
                                        color = ColorUtil.mainFont,
                                        fontWeight = FontWeight.Normal
                                    )
                                }
                            )
                        }
                    }

                    Box(modifier = Modifier.weight(1f),
                        contentAlignment = Alignment.Center) {
                        Button(colors = buttonColors(backgroundColor = ColorUtil.primary),
                            enabled = installEnable.value,
                            onClick = {
                            installEnable.value = false
                                installAab()
                            }) {
                            Text(
                                text = "安  装",
                                color = if (installEnable.value) ColorUtil.mainFont else ColorUtil.disableFont,
                                textAlign = TextAlign.Center,
                                fontWeight = FontWeight.Bold,
                                fontSize = 20.sp,
                                modifier = Modifier.width(80.dp)
                            )
                        }
                    }
                }
                Spacer(modifier = Modifier.height(16.dp))
                showLog()
            }
        }
        LaunchedEffect("") {
            initLog()
        }
    }
}

@Composable
fun menu() {
    Row (modifier = Modifier.padding(16.dp,0.dp).fillMaxWidth().height(32.dp)){
        val signDialog = remember { mutableStateOf(false) }
        TextButton(modifier = Modifier.padding(0.dp), onClick = {
            signDialog.value = true
        }) {
            Text("Settings", color = ColorUtil.mainFont)
        }
        keyStoreManager(signDialog)
    }

}

@Composable
fun selectFile(focusRequester: FocusRequester) {
    val density = LocalDensity.current.density
    val dropParent = remember(density) {
        PlatformDropTargetModifier(
            density = density,
            window = parentWindow,
        )
    }
    val alertDialog = remember { mutableStateOf(false) }
    Column(
        modifier = Modifier.then(dropParent).border(1.dp, ColorUtil.border, RoundedCornerShape(16.dp))
            .fillMaxWidth()
            .wrapContentHeight()
            .dropTarget(
                onDragStarted = { _, _ ->
                    true
                },
                onDropped = { uris, _ ->
                    var hasAabFile = false
                    uris.forEach {
                        if (it is FileUri) {
//                            println("path:${it.path}  :${it.mimeType} ${it.extension}")
                            if (it.extension == suffix) {
                                pathStr.value = it.path
                                aabFile = it.file
                                hasAabFile = true
                            }
                        }
                    }
                    if (!hasAabFile) {
                        alertDialog.value = true
                    }
                    false
                }),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Spacer(modifier = Modifier.height(16.dp))
        Text("把.aab文件拖拽到这里或者选择.aab文件")
        Spacer(modifier = Modifier.height(16.dp))
        Row(verticalAlignment = Alignment.Bottom) {
            var isFocus by remember {  mutableStateOf(false) }
            TextField(
                value = pathStr.value,
                modifier = Modifier.sizeIn(minWidth = 494.4.dp, maxWidth = 494.4.dp)
                    .focusRequester(focusRequester).defaultMinSize(2.dp,1.dp)
                    .onFocusChanged {
                                    isFocus = it.hasFocus
                    },
                singleLine = true,
//                readOnly = true,
                enabled = installEnable.value,
                onValueChange = { str ->
                    pathStr.value = str
                },
                label = { Text(".aab文件路径") },
                placeholder = { Text(text = "请输入/选择/拖入aab文件", fontSize = 12.sp) },
                colors = textFieldColors(
                    textColor = ColorUtil.mainFont,
                    unfocusedIndicatorColor = ColorUtil.subFont,
                    focusedIndicatorColor = ColorUtil.subFont,
                    unfocusedLabelColor = ColorUtil.subFont,
                    focusedLabelColor = ColorUtil.subFont,
                    backgroundColor = Color.Transparent,
                    cursorColor = ColorUtil.subFont
                ),
                textStyle = TextStyle(fontSize = 12.sp)
            )
            rememberCoroutineScope().launch {
                focusRequester.requestFocus()
            }
            Spacer(modifier = Modifier.height(16.dp))
            var isEnable by remember { mutableStateOf(true) }
            folderButton(isEnable,isFocus) {
                isEnable = false
                coroutineScope.launch(Dispatchers.IO) {
                    val path = PropertiesUtil.getProperties(AAR_PATH,"inputPathKey") ?: ""
                    val cd = File(path)
                    if (cd.exists()) {
                        fileChooser.currentDirectory = cd
                    }
                    fileChooser.fileFilter = filter
                    fileChooser.addChoosableFileFilter(filter)
                    if (fileChooser.showOpenDialog(parentWindow) == JFileChooser.APPROVE_OPTION) {
                        val f: File = fileChooser.selectedFile
                        if (f.extension == suffix) {
                            pathStr.value = f.absolutePath
                            aabFile = f
                        } else {
                            alertDialog.value = true
                        }
                    }
                    isEnable = true
                }
            }

            errorDialog(title = remember { mutableStateOf("错误") },
                tipContent = remember { mutableStateOf("选择的文件格式错误") },
                alertDialog)
        }
        Spacer(modifier = Modifier.height(16.dp))
    }
}

@Composable
fun showLog() {
    val listState = rememberLazyListState()
    Box(modifier = Modifier.fillMaxHeight()
        .fillMaxWidth()
        .background(color = ColorUtil.background)
        .padding(16.dp,8.dp,8.dp,8.dp)){
        LazyColumn(modifier = Modifier.fillMaxWidth()
            .fillMaxHeight(),
            state = listState,
            verticalArrangement = Arrangement.spacedBy(4.dp)
        ) {
//            item {
//                Box(modifier = Modifier.fillMaxWidth()
//                ) { Text("日志") }
//            }
            items(installSteps.size){
                Box(modifier = Modifier.fillMaxWidth()
                ) { Text(
                    installSteps[it],
                    color = ColorUtil.subFont,
                    fontSize = 10.sp) }
                coroutineScope.launch {
                    listState.animateScrollToItem(it)
                }
            }
        }
        //FIXME 一拖动就卡死，不知哪里写错了，还是有bug
//        VerticalScrollbar(
//            modifier = Modifier.fillMaxHeight().align(Alignment.CenterEnd),
//            adapter = rememberScrollbarAdapter(
//                scrollState = listState
//            )
//        )
    }
}

@Composable
fun folderButton(isEnable:Boolean,isFocus:Boolean,onClick: () -> Unit){
    Column(horizontalAlignment = Alignment.End, modifier = Modifier.sizeIn(minWidth = 72.dp, maxWidth = 72.dp)) {
        TextButton(onClick = onClick,
            colors = buttonColors(backgroundColor = Color.White),
            enabled = installEnable.value && isEnable,
            modifier = Modifier.height(36.dp).padding(2.dp).fillMaxWidth()) {
            Image(painter = painterResource("ic_folder.svg"),"",
                colorFilter= if (installEnable.value)ColorFilter.tint(ColorUtil.selectFont) else ColorFilter.tint(ColorUtil.disableFont) )
        }

        Box(modifier = Modifier.height(if (isFocus)2.dp else 1.1.dp).fillMaxWidth().background(color = if (installEnable.value) ColorUtil.border else ColorUtil.disableBorder))
    }
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
@Preview
fun <T> Dropdown(items:Iterable<T>,
                 onMenuItemClick: (item:T) -> Unit,
                 tip:String,
                 onclick:(()->Unit)? = null,
                 tipColor:Color = ColorUtil.mainFont,
                 lineColor: Color = ColorUtil.mainFont,
                 enabled:Boolean = true,
                 strokeWidth: Float = 1f,
                 minWidth:Dp = 110.dp,
                 maxWidth:Dp = 142.dp,
                 menuMaxHeight: Dp = Dp.Unspecified,
                 menuItemContent: @Composable (RowScope.(item:T) -> Unit)) {
//    Column {
    var expanded by remember { mutableStateOf(false) }
    var childWidth = 0f
    Surface(onClick = {
        onclick?.invoke()
        expanded = true
    }, color = Color.Transparent,
        enabled = enabled,
        modifier = Modifier
            .drawBehind {
                childWidth = size.width/density
                val sw = strokeWidth * density
                val y = size.height - sw / 2
//                println("strokeWidth:${sw}  y:${y}")
                drawLine(
                    color = lineColor, start = Offset(0f, y),
                    end = Offset(size.width, y),
                    strokeWidth = sw
                )
            }) {
        Row(modifier = Modifier.sizeIn(minWidth = minWidth,maxWidth = maxWidth),
            verticalAlignment = Alignment.CenterVertically) {
            Text(text = tip, modifier = Modifier
                .sizeIn(minWidth = (minWidth-32.dp),maxWidth = maxWidth - 32.dp)
                .padding(16.dp,8.dp),color = tipColor)
            Image(
                painterResource("ic_direction_down.svg"), "",
                modifier = Modifier.size(32.dp, 16.dp).scale(1.2f, 1f),
                colorFilter = ColorFilter.tint(tipColor)
            )
        }

        DropdownMenu(expanded = expanded,
            modifier = Modifier.sizeIn(minWidth = childWidth.dp, maxHeight = menuMaxHeight),
            onDismissRequest = {
                expanded = false
            }) {
            items.forEach {
                DropdownMenuItem(onClick = {
                    expanded = false
                    onMenuItemClick.invoke(it)
                }) {
                    menuItemContent.invoke(this,it)
                }
            }
        }

    }
}

suspend fun refreshDevices(){
    val ds = AdbUtil.getDevices()
    if (ds.isNotEmpty()){
        devices.clear()
        devices.addAll(ds)
        if (phoneBean.value.transportId < 0) {
            phoneBean.value = devices[0]
        }
        installSteps.add("List of devices attached")
        devices.forEach {
            installSteps.add("${it.usbName}      ${it.model}")
        }
    } else {
        devices.clear()
        phoneBean.value = PhoneBean("","","no device","",-1)
        installSteps.add("No devices")
    }
}

/**
 * 第一步:检测链接的手机/aab文件
 * 第二步:验证签名文件
 * 第三步:build-apks
 * 第四步：安装
 */
private fun installAab(){
    coroutineScope.launch(Dispatchers.IO) {
        val installBean = InstallBean()
        if (verificationLocal(installBean)){
            if (verificationKeyStore(installBean)) {
                if (buildApks(installBean)) {
                    installApks(installBean)
                    installEnable.value = true
                    PropertiesUtil.setProperties(AAR_PATH,"inputPathKey",installBean.aabFile?.absolutePath?:"")
                }
            }
        }
    }
}

private fun verificationLocal(installBean: InstallBean) :Boolean{
    var isAabFile = false
    val lAabFile = aabFile
    if (lAabFile?.exists() == true){
        isAabFile = true
        installBean.aabFile = lAabFile
    } else {
        if (pathStr.value.isNotEmpty()){
            val file = File(pathStr.value)
            if (file.exists() && file.extension == suffix){
                isAabFile = true
                installBean.aabFile = file
            }
        }
    }
    if (isAabFile) {
        installSteps.add("Check File OK")
    } else {
        installSteps.add("No AAB file found")
        installEnable.value = true
        return false
    }
    val phone = phoneBean.value
    if (phone.transportId > 0) {
        installSteps.add("Will be installed to ${phone.model}")
        installBean.phone = phone
    } else {
        installSteps.add("No Devices")
        installEnable.value = true
        return false
    }

    val adbFile = File(AdbUtil.adbFilePath,"adb.exe")
    if (!(adbFile.exists() && adbFile.isFile)) {
        installSteps.add("Find Adb Fail")
        installEnable.value = true
        return false
    }
    installBean.adbFilePath = adbFile.absolutePath
    return true
}

private suspend fun verificationKeyStore(installBean: InstallBean):Boolean{
    val keyStores = getKeyStores()
    val keyStore = keyStores.find { it.isDefault }
    if (keyStore != null) {
        val vsb = KeyStoreBean(keyStorePath = keyStore.keyStorePath,
            keyStorePassword = keyStore.keyStorePassword)
        vsb.alias.add(KeyStoreAlias(keyStore.alias,keyStore.aliasPassword))
        val result = KeyStoreUtil.verificationAlias(vsb,keyStore.alias,keyStore.aliasPassword)
        if (result.isSuccess()) {
            installBean.keyStorePath = keyStore.keyStorePath
            installBean.keyStorePassword = keyStore.keyStorePassword
            installBean.alias = keyStore.alias
            installBean.aliasPassword = keyStore.aliasPassword
            installSteps.add("Check KeyStore OK")
        } else {
            installSteps.add(result.errorMess)
            installEnable.value = true
            return false
        }
    } else {
        installSteps.add("No default KeyStore")
        installEnable.value = true
        return false
    }
    return true
}

private suspend fun buildApks(installBean: InstallBean):Boolean{
    installSteps.add("Start Build...")
    val buildResult = BundleToolUtil.buildApks(installBean)
    if (buildResult.isSuccess()) {
        buildResult.message.forEach{
            installSteps.add(it)
        }
        installSteps.add("Build OK,Use Time:${buildResult.useTime}s")
    } else {
        buildResult.message.forEach{
            installSteps.add(it)
        }
        installEnable.value = true
        return false
    }
    return true
}

private suspend fun installApks(installBean: InstallBean):Boolean {
    installSteps.add("Start Install...")
    val buildResult = BundleToolUtil.installApks(installBean)
    if (buildResult.isSuccess()) {
        buildResult.message.forEach{
            installSteps.add(it)
        }
        installSteps.add("Install OK,Use Time:${buildResult.useTime}s")
    } else {
        buildResult.message.forEach{
            installSteps.add(it)
        }
        installEnable.value = true
        return false
    }
    return true
}

private fun initLog() {
    val logPath = Util.logPath
    val cachePath = Util.logCachePath
    try {
        val xlog = XLog().apply {
            setConsoleLogOpen(0,false)
            appenderOpen(XLog.LEVEL_INFO, XLog.AppednerModeAsync, cachePath, logPath, "log", 0)
        }
        Log.setLogImp(xlog)
    } catch (_:Exception) { }
}

fun main() = application {

    coroutineScope = rememberCoroutineScope()
    devices = remember { mutableStateListOf() }
    pathStr  = remember { mutableStateOf("") }
    phoneBean = remember { mutableStateOf(PhoneBean("","","no device","",-1)) }
    installSteps = remember { mutableStateListOf() }
    installEnable = remember { mutableStateOf(true) }
    val isShowTray = remember { mutableStateOf(false) }
    if (isShowTray.value){
        Log.appenderFlushSync(true)
        Tray(icon= painterResource("icon.png"),
            tooltip = "aab install tool",
            onAction = {
                Log.i("this is double click")
                isShowTray.value = false
            },
            menu = {
//                Menu(text = "退出")
                Item(text = "  显示主界面  ", onClick ={
                    isShowTray.value = false
                })
                Separator()
                Item(text = "  退  出   ", onClick ={
                    Log.appenderFlushSync(true)
                    exitApplication()
                })
            }
        )
    } else {
        Window(
//        onCloseRequest = ::exitApplication,
            onCloseRequest = {
                isShowTray.value = true
            },
            title = "aab install tool", resizable = false,
            state = WindowState(size = DpSize(800.dp, 494.4.dp)),
            icon = painterResource("icon.png")
        ) {
            parentWindow = window
            //好丑
//        MenuBar{
//            Menu("File") {
//                Item("New window", onClick = { } )
//            }
//        }
//        TopAppBar {
//            Text("这是显示在什么位置的")
//        }
//        BottomAppBar {
//            Text("这是bottom AppBar")
//        }
//        Snackbar { Text("这是显示在什么位置的") }
//        println("机智如妖")
            App()
            coroutineScope.launch(Dispatchers.IO) {
                refreshDevices()
            }
        }
    }
}

fun getPosition(wWidth:Float,wHeight:Float):WindowPosition {
    val w = wWidth/2
    val h = wHeight/2

    Log.i("screenInsets:${screenInsets}")
    val px = positionCompute(parentWindow.x, parentWindow.width,w, screenSize.width - screenInsets.left - screenInsets.right)
    val py = positionCompute(parentWindow.y, parentWindow.height,h, screenSize.height - screenInsets.top - screenInsets.bottom)
    return WindowPosition(px.dp,py.dp)
}

private fun positionCompute(p:Int,pSize:Int,cSize:Float,sSize:Int):Float{
    val position =  if (p <= 0) {
        (p + pSize)/2f - cSize
    } else if (p +pSize >= sSize) {
        (p + sSize)/2f - cSize
    } else {
        p + pSize /2f - cSize
    }
    return kotlin.math.min(kotlin.math.max(0f, position), sSize - 2*cSize)
}